﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using HgRemoteContracts;
using System.ServiceModel;
using System.Threading;

namespace HgRemoteServer
{
	public class CmdStreamer
	{
		private static Dictionary<Guid, CmdStreamer> streamers = new Dictionary<Guid, CmdStreamer>();

		private readonly Guid id;
		Process p;
		Queue<ProcessStartInfo> startInfos = new Queue<ProcessStartInfo>();
		private OperationContext opContext;
		private IHgCallback callback;
		private Dictionary<string, string> environmentVars = new Dictionary<string, string>();
		private List<string> finishMessages = new List<string>();

		public object State { get; set; }

		public CmdStreamer(string cmd, string arguments, string wd)
		{
			id = Guid.NewGuid();

			AddCommand(cmd, arguments, wd);

			opContext = OperationContext.Current;
			callback = opContext.GetCallbackChannel<IHgCallback>();
		}

		private void ReceiveData(string data, bool isStdErr)
		{
			SendData(data + Environment.NewLine);
			if (EvalStatusLine != null && data != null)
				EvalStatusLine(this, new CmdStreamerEventArgs(this, data));
		}

		public void AddFinishMessage(string message)
		{
			finishMessages.Add(message);
		}

		public void SendData(string data)
		{
			callback.SendResponse(data);
		}

		public void AddCommand(string cmd, string arguments, string wd)
		{
			ProcessStartInfo psi = new ProcessStartInfo(cmd, arguments);
			psi.WorkingDirectory = wd;
			startInfos.Enqueue(psi);
		}

		public void SetEnvironmentVar(string var, string value)
		{
			environmentVars[var] = value;
		}

		public event CmdStreamerEventHandler EvalStatusLine;

		public void Run()
		{
			streamers.Add(this.id, this);
			Thread t = new Thread(this.RunWorker);
			t.Start();
		}

		private void RunWorker()
		{
			while (startInfos.Count > 0)
			{
				ProcessStartInfo psi = startInfos.Dequeue();
				psi.UseShellExecute = false;
				psi.CreateNoWindow = true;

				psi.RedirectStandardError = true;
				psi.RedirectStandardOutput = true;

				foreach (var set in environmentVars)
					psi.EnvironmentVariables.Add(set.Key, set.Value);

				p = new Process();
				p.OutputDataReceived += (s, e) => ReceiveData(e.Data, false);
				p.ErrorDataReceived += (s, e) => ReceiveData(e.Data, true);
				p.StartInfo = psi;
				p.Start();
				p.BeginOutputReadLine();
				p.BeginErrorReadLine();

				p.WaitForExit();
				Thread.Sleep(500);
			}

			if (finishMessages.Count > 0)
				SendData(String.Join("", finishMessages.Select(msg => Environment.NewLine + msg).ToArray()));
			
			callback.Finished();
			opContext.Channel.Close();
			streamers.Remove(this.id);
		}
	}

	public delegate void CmdStreamerEventHandler(object source, CmdStreamerEventArgs e);

	public class CmdStreamerEventArgs : EventArgs
	{
		public CmdStreamer Cmd { get; private set; }
		public string ResultLine { get; private set; }

		public CmdStreamerEventArgs(CmdStreamer src, string line)
		{
			this.Cmd = src;
			this.ResultLine = line;
		}
	}
}
